/*
 * @Author: czy
 * @Date: 2021-04-26 07:41:28
 * @LastEditTime: 2021-05-03 22:24:57
 */
#include "myarray.h"
#include "mystring.h"
#include "mystdio.h"
#include "exception.h"
#include "shell.h"

static historyCommand history_command;
static command commands[COMMAND_SIZE];
static int command_len = 0;

/**
 * @description: Add a command to the history command.
 * @param str   Command string.
 */
static void add_history_command(char *str)
{
    if (history_command.length < HISTORY_SIZE)
    {
        strcpy(history_command.commands[history_command.length], str);
        history_command.length++;
        history_command.current = history_command.length;
    }
    else
    {
        for (int i = 0; i < HISTORY_SIZE - 1; i++)
        {
            strcpy(history_command.commands[i], history_command.commands[i + 1]);
        }
        strcpy(history_command.commands[HISTORY_SIZE - 1], str);
        history_command.current = HISTORY_SIZE;
    }
}

/**
 * @description: Print history command records.
 * @param num   Number of commands. (-1 for all)
 */
void print_history_command(int num)
{
    if (num < 0 || num > history_command.length)
    {
        num = history_command.length;
    }
    for (int i = history_command.length - num; i < history_command.length; i++)
    {
        myprintk("%2d: %s\n", i, history_command.commands[i]);
    }
}

/**
 * @description: Cursor movement.
 * @param ins       Instruction.
 * @param direction LEFT or RIGHT.
 */
static void move(instruction *ins, int direction)
{
    if (direction == LEFT && (ins->current > ins->head))
    {
        ins->current--;
        cursor_offset(-1);
        ins->output_str_len++;
    }
    if (direction == RIGHT && (ins->current < ins->tail))
    {
        ins->current++;
        cursor_offset(1);
        ins->output_str_len--;
    }
}

/**
 * @description: Insert a character at the cursor of the current instruction.
 * @param ins   Instruction.
 * @param ch    Character.
 */
static void insert(instruction *ins, char ch)
{
    strinsert(ins->head, ins->current - ins->head, ch);
    ins->tail++;
    ins->current++;
    *(ins->tail) = '\0';
}

/**
 * @description: Delete a character at the cursor of the current instruction.
 * @param ins   Instruction.
 */
static void delete (instruction *ins)
{
    if (ins->current > ins->head)
    {
        strdelete(ins->head, ins->current - ins->head - 1);
        ins->tail--;
        ins->current--;
        *(ins->tail) = '\0';
        myPutchar(127);
    }
}

/**
 * @description: Submit the current instruction to the shell.
 * @param ins   Instruction.
 */
static void submit(instruction *ins)
{
    int flag = FALSE;
    int argc;
    char *argv[MAX_ARGC];
    command cmd;
    myprintk("\n");
    deblank(ins->head);
    if (*(ins->head) != '\0')
    {
        add_history_command(ins->head);
    }
    argc = split(argv, ins->head, " ");
    if (argc > 0 && *argv[0] != '\0')
    {
        for (int i = 0; i < command_len; i++)
        {
            if (strcmp(argv[0], commands[i].name) == 0)
            {
                commands[i].func(argc, argv);
                flag = TRUE;
                break;
            }
        }
        if (flag == FALSE)
        {
            myprintk("%s: command not found\n", argv[0]);
        }
    }
    myPrintk(GREEN, PROMPT);
    ins->current = ins->head;
    ins->tail = ins->head;
    strzero(ins->head, BUFFER);
    ins->output_str_len = 0;
}

/**
 * @description: Replace the current instruction.
 * @param ins   Instruction.
 */
static void replace(instruction *ins, char *new_ins)
{
    strcpy(ins->head, new_ins);
    ins->tail = ins->head + strlen(new_ins);
    ins->current = ins->tail;
}

/**
 * @description: Complete the current instruction.
 * @param ins   Instruction.
 */
static void complete(instruction *ins)
{
    if (*(ins->head) == '\0')
    {
        return;
    }
    int count = 0;
    int space;
    int result[COMMAND_SIZE];
    char temp[BUFFER] = "";
    strcpy(temp, ins->head);
    space = deblank(temp);
    for (int i = 0; i < command_len; i++)
    {
        if (strindex(temp, commands[i].name) == 0)
        {
            result[count++] = i;
        }
    }
    if (count == 0)
    {
        return;
    }
    if (count == 1)
    {
        strcpy(temp, commands[result[0]].name);
        fill(temp, ' ', space, RIGHT, TRUE);
        replace(ins, temp);
    }
    else
    {
        myprintk("\n");
        for (int i = 0; i < count; i++)
        {
            myprintk("%s\t", commands[result[i]].name);
        }
        myprintk("\n");
        myPrintk(GREEN, PROMPT);
    }
}

/**
 * @description: Clear the current instruction.
 * @param ins   Instruction.
 */
static void clear_command(instruction *ins)
{
    char *p = ins->tail;
    if (ins->output_str_len > 0)
    {
        cursor_offset(ins->output_str_len + 1);
        p = p + ins->output_str_len;
    }
    while (p > ins->head)
    {
        myPutchar(127);
        p--;
    }
}

/**
 * @description: Print the current instruction.
 * @param ins   Instruction.
 */
void print_command(instruction *ins)
{
    clear_command(ins);
    myprintf("%s", ins->head);
    cursor_offset(ins->current - ins->tail);
}

/**
 * @description: Replaces the current instruction with the previous instruction in the history.
 * @param ins   Instruction.
 */
static void previous_command(instruction *ins)
{
    clear_command(ins);
    if (history_command.current > 0)
    {
        history_command.current--;
        replace(ins, history_command.commands[history_command.current]);
    }
}

/**
 * @description: Replaces the current instruction with the next instruction in the history.
 * @param ins   Instruction.
 */
static void next_command(instruction *ins)
{
    clear_command(ins);
    if (history_command.current < history_command.length)
    {
        history_command.current++;
        replace(ins, history_command.commands[history_command.current]);
    }
}

/**
 * @description: Add a command to the command list.
 * @param name          Name of the command.
 * @param help_content  Help content of the command
 * @param func          Function.
 */
void addcmd(char *name, char *help_content, int (*func)(int argc, char *argv[]))
{
    command cmd;
    strcpy(cmd.name, name);
    strcpy(cmd.help_content, help_content);
    cmd.func = func;
    if (command_len < COMMAND_SIZE)
    {
        commands[command_len] = cmd;
        command_len++;
    }
}

/**
 * @description: Get all commands from the command list.
 * @param cmds      Return the list of commands.
 * @param length    Returns the number of commands.
 */
void getcmd(command *cmds, int *length)
{
    *length = command_len;
    for (int i = 0; i < command_len; i++)
    {
        cmds[i] = commands[i];
    }
}

/**
 * @description: Match the input command with the registered command.
 * @param name  Name of command.
 * @param cmd   Matching command.
 * @return TRUE if match, else FALSE.
 */
int match(char *name, command *cmd)
{
    for (int i = 0; i < command_len; i++)
    {
        if (strcmp(name, commands[i].name) == 0)
        {
            *cmd = commands[i];
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * @description: Initialization of shell.
 */
static void init(void)
{
    myprintk("Press any key to start shell...");
    register_command();
    myGetchar();
    clear();
    myPrintk(GREEN, PROMPT);
    history_command.current = 0;
    history_command.length = 0;
}

/**
 * @description: Decode the input character.
 * @param ch    Input character.
 * @return Decoded character.
 */
static int decode(char ch)
{
    if (ch == 27)
    {
        if (myGetchar() == 91)
        {
            switch (myGetchar())
            {
            case 65:
                return UP_DIRECTION;
            case 66:
                return DOWN_DIRECTION;
            case 67:
                return RIGHT_DIRECTION;
            case 68:
                return LEFT_DIRECTION;
            default:
                return 0;
            }
        }
    }
    return ch;
}

/**
 * @description: Start shell.
 */
void startShell(void)
{
    int ch;
    char str[BUFFER] = "";
    instruction ins = {str, str, str, 0};
    int flag;

    init();

    while (1)
    {
        ch = myGetchar();
        flag = TRUE;
        switch (decode(ch))
        {
        case '\t':
            complete(&ins);
            break;
        case '\r':
            submit(&ins);
            break;
        case 127:
            delete (&ins);
            break;
        case LEFT_DIRECTION:
            flag = FALSE;
            move(&ins, LEFT);
            break;
        case RIGHT_DIRECTION:
            flag = FALSE;
            move(&ins, RIGHT);
            break;
        case UP_DIRECTION:
            previous_command(&ins);
            break;
        case DOWN_DIRECTION:
            next_command(&ins);
            break;
        default:
            if (ch >= 32 && ch <= 126)
            {
                insert(&ins, ch);
            }
            break;
        }
        if (flag)
        {
            print_command(&ins);
        }
    }
}
